﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TraceMessage.cs" company="AzureContrib (Open source library http://azurecontrib.codeplex.com)">
//   Microsoft Public License (Ms-PL)
// </copyright>
// <summary>
//   A <see cref="Message"/> to trace to a given <see cref="Level"/> for a given <see cref="Switch"/>.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Diagnostics.CodeAnalysis;
using System.Web.Script.Serialization;

using AzureContrib.WindowsAzure.Properties;

namespace AzureContrib.WindowsAzure.Diagnostics
{
	/// <summary>
	/// A <see cref="TraceMessageBase.Message"/> to trace to a given <see cref="AzureContrib.WindowsAzure.Diagnostics.TraceMessageBase.Level"/> for a given <see cref="AzureContrib.WindowsAzure.Diagnostics.TraceMessageBase.Switch"/>.
	/// </summary>
	/// <remarks>Serialized to a string as JSON.</remarks>
	[Serializable]
	public class TraceMessage : TraceMessageBase
	{
		/// <summary>
		/// 64 * 1024 bytes  = 64KB
		/// </summary>
		public const int MaxMessageBuffer = 64 * 1024;

		/// <summary>
		/// Initializes a new instance of the <see cref="TraceMessage"/> class.
		/// </summary>
		public TraceMessage()
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="TraceMessage"/> class.
		/// </summary>
		/// <param name="level">The <see cref="LogLevel"/> used for this Message.</param>
		/// <param name="switch">The focused tracing area.</param>
		/// <param name="message">The Message traced.</param>
		public TraceMessage(LogLevel level, string @switch, string message)
			: base(level, @switch, message)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="TraceMessage"/> class.
		/// </summary>
		/// <param name="category">The category for the trace. It is recommended when possible to use <see cref="TraceMessage(LogLevel,string,string)"/>.</param>
		/// <param name="switch">The focused tracing area.</param>
		/// <param name="message">The Message traced.</param>
		public TraceMessage(string category, string @switch, string message)
			: base(category, @switch, message)
		{
		}

		private static JavaScriptSerializer JavaScriptSerializer
		{
			get
			{
				return new JavaScriptSerializer
					{
						MaxJsonLength = MaxMessageBuffer
					};
			}
		}

		/// <summary>
		/// Is the <paramref name="message"/> a valid <see cref="string"/> represenation of a <see cref="TraceMessage"/>.
		/// </summary>
		/// <param name="message">The Message as a <see cref="string"/>.</param>
		/// <returns>True if the Message is validated.</returns>
		public static bool IsValidMessage(string message)
		{
			if (string.IsNullOrEmpty(message))
			{
				return false;
			}

			TraceMessage traceMessage;
			try
			{
				// Implicit cast to TraceMessage.
				traceMessage = message;
			}
			catch (ArgumentNullException)
			{
				return false;
			}
			catch (ArgumentException)
			{
				return false;
			}
			catch (InvalidOperationException)
			{
				return false;
			}

			// traceMessage.Level must have been deserialized or exception.
			return !string.IsNullOrEmpty(traceMessage.Message) &&
			       !string.IsNullOrEmpty(traceMessage.Switch);
		}

		/// <summary>
		/// Try to parse out a <see cref="TraceMessage"/> fro the <paramref name="message"/>
		/// </summary>
		/// <param name="message">The <see cref="string"/> Message to try to parse.</param>
		/// <param name="traceMessage">The resulting parsed <see cref="TraceMessage"/> or null.</param>
		/// <returns>True if the <paramref name="message"/> is a <see cref="TraceMessage"/>.</returns>
		public static bool TryParse(string message, out TraceMessage traceMessage)
		{
			traceMessage = null;

			if (IsValidMessage(message))
			{
				traceMessage = message;
			}

			return traceMessage != null;
		}

		/// <summary>
		/// Turn a <see cref="string"/> into a <see cref="TraceMessage"/> instance.
		/// </summary>
		/// <param name="traceMessage">The <see cref="TraceMessage"/> as a <see cref="string"/></param>
		/// <returns>A <see cref="TraceMessage"/> created from the <see cref="string"/>.</returns>
		public static TraceMessage ConvertFromString(string traceMessage)
		{
			Assumes.NotNull(traceMessage, "traceMessage");
			return traceMessage;
		}

		/// <summary>
		/// Turn a <see cref="string"/> into a <see cref="TraceMessage"/>.
		/// </summary>
		/// <param name="traceMessage">The <see cref="TraceMessage"/> as a <see cref="string"/>.</param>
		/// <returns>A <see cref="TraceMessage"/> created from the incomming <see cref="string"/>.</returns>
		/// <exception cref="ArgumentException">If the <paramref name="traceMessage"/> is not a valid serialized <see cref="TraceMessage"/>.</exception>
		/// <exception cref="ArgumentOutOfRangeException">If the <paramref name="traceMessage"/> is empty.</exception>
		/// <exception cref="InvalidOperationException">There is a problem deserializing the <paramref name="traceMessage"/>.</exception>
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "AzureContrib.WindowsAzure.Core.Assumes.NotNull handles null checking.")]
		public static implicit operator TraceMessage(string traceMessage)
		{
			Assumes.NotNull(traceMessage, "traceMessage");
			Assumes.NotNullOrEmpty(traceMessage, "traceMessage", Strings.NullOrEmptyTraceMessageString);
			TraceMessage result = null;
			try
			{
				result = JavaScriptSerializer.Deserialize<TraceMessage>(traceMessage);
			}
			catch (ArgumentException e)
			{
				// Could be a Message prefixed by a custom category. Try to strip of the category part and deserialize the rest.
				if (e.Message.StartsWith("Invalid JSON primitive: ", StringComparison.Ordinal) && traceMessage.Contains(": {"))
				{
					var actualTraceMessage = traceMessage.Substring(traceMessage.IndexOf(':') + 1).Trim();
					result = JavaScriptSerializer.Deserialize<TraceMessage>(actualTraceMessage);
				}
				else
				{
					throw;					
				}
			}

			return result;
		}

		/// <summary>
		/// Try to get the <see cref="LogLevel"/> for this trace Message.
		/// </summary>
		/// <param name="logLevel">The log level.</param>
		/// <returns>True if there was a log level defined for the Message.</returns>
		public bool TryGetLevel(out LogLevel logLevel)
		{
			return Level.TryParseLogLevel(out logLevel);
		}

		/// <summary>
		/// Turn a <see cref="string"/> into a <see cref="TraceMessageBase"/> instance.
		/// </summary>
		/// <param name="traceMessage">The <see cref="TraceMessageBase"/> as a <see cref="string"/></param>
		/// <returns>A <see cref="TraceMessageBase"/> created from the <see cref="string"/>.</returns>
		protected override TraceMessageBase FromString(string traceMessage)
		{
			return ConvertFromString(traceMessage);
		}

		/// <summary>
		/// The <see cref="TraceMessage"/> as a <see cref="string"/>.
		/// </summary>
		/// <param name="traceMessageBase">The <see cref="TraceMessageBase"/> to convert.</param>
		/// <returns>The <see cref="string"/>.</returns>
		protected override string ToString(TraceMessageBase traceMessageBase)
		{
			return JavaScriptSerializer.Serialize(this);
		}
	}
}